package isssr.dao.hibernate;

import isssr.dao.exception.DAOException;
import isssr.dao.exception.HibernateDAOException;
import isssr.dao.interfaces.RecordPagination;
import isssr.dao.interfaces.RecordSorting;
import isssr.dao.interfaces.UserDAO;
import isssr.entity.src.ComparisonOperator;
import isssr.entity.src.OrganizationalGoal;
import isssr.entity.src.Role;
import isssr.entity.src.User;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.eclipse.jdt.internal.compiler.ast.OperatorExpression;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.criterion.SimpleExpression;
import org.hibernate.internal.CriteriaImpl.CriterionEntry;

public class HibernateUserDAO extends HibernateDAOCommons implements UserDAO, Serializable {

	
	
	private String defaultAliasName;
	
	public HibernateUserDAO(SessionFactory sessionFactory) 
	{
		super(sessionFactory);
		this.defaultAliasName = "user" ;
	}
	
	private void addFilteringToCriteria(Criteria criteria,
			Map<String, Object> filters) {
		
		if(filters == null || filters.isEmpty())
			return;
		
		String label;
		
		for(Entry<String, Object> entry : filters.entrySet())
		{
			label = entry.getKey();
			
			if(label.equals("username"))
			{
				addLikeCriterion(criteria, entry);
			}
			else if(label.equals("email"))
			{
				addLikeCriterion(criteria, entry);
			}
			else if(label.equals("firstname"))
			{
				addLikeCriterion(criteria, entry);
			}
			else if(label.equals("lastname"))
			{
				addLikeCriterion(criteria, entry);
			}
			else if(label.equals("role"))
			{
				addRoleCriterion(criteria, entry);
			}
		}
	}

	
	
	private void addRoleCriterion(Criteria criteria, Entry<String, Object> parameter) 
	{
		String value = (String)parameter.getValue();
		
		String roleAlias = "role";
		String roleReference = this.defaultAliasName + ".role";
		
		criteria.createAlias(roleReference, roleAlias);
		criteria.add(Restrictions.like(roleAlias + ".title", value, MatchMode.ANYWHERE));
	}

	
	private void addLikeCriterion(Criteria criteria, Entry<String, Object> parameter)
	{
		String label = parameter.getKey() ;
		String value = (String) parameter.getValue();
		
		Criterion criterion = Restrictions.like(label, value, MatchMode.ANYWHERE);
		
		criteria.add(criterion);
	}


	private void addOrderingToCriteria(Criteria criteria, RecordSorting ordering) 
	{
		if(ordering == null || ordering.getField() == null)
			return;
		
		switch(ordering.getOrder())
		{
			case ASCENDING: 
				criteria.addOrder(Order.asc(ordering.getField()));
				break;
			case DESCENDING:
				criteria.addOrder(Order.desc(ordering.getField()));
				break;
		}
	}
	
	private void addPagingToCriteria(Criteria criteria, RecordPagination pagination) {

		if(pagination == null)
			return;
		
		if(pagination.firstResult != null && pagination.firstResult>0)
			criteria.setFirstResult(pagination.firstResult);
		
		if(pagination.pageSize != null && pagination.pageSize>0)
			criteria.setFetchSize(pagination.pageSize);
		
	}
	

	private void addSuperRestrictionToCriteria(Criteria criteria,
			User referenceUser) {
		
		Number referenceLevel = null;
		Number referenceID = null;
		
		try 
		{
			referenceLevel = extractLevelFromUser(referenceUser);
			referenceID = (Number) extractFieldFromUser(referenceUser, "uid");
		} 
		catch (ReflectiveOperationException e) 
		{
			e.printStackTrace();
		}
		
		
		String roleAliasName = "__role";
		String levelLabel = "level";
		
		criteria.createAlias(this.defaultAliasName + ".role", roleAliasName);
		
		criteria.add(Restrictions.ge(roleAliasName + "." + levelLabel, referenceLevel));
		
		criteria.add(Restrictions.not(Restrictions.idEq(referenceID)));
	}

	private void addEnactorRestrictionToCriteria(Criteria criteria,
			User referenceUser) {
		
		Number referenceLevel = null;
		Number referenceID = null;
		
		try 
		{
			referenceLevel = extractLevelFromUser(referenceUser);
			referenceID = (Number) extractFieldFromUser(referenceUser, "uid");
		} 
		catch (ReflectiveOperationException e) 
		{
			e.printStackTrace();
		}
		

		String roleAliasName = "__role";
		String levelLabel = "level";
		
		criteria.createAlias(this.defaultAliasName + ".role", roleAliasName);
		
		criteria.add(Restrictions.le(roleAliasName + "." + levelLabel, referenceLevel));
	}

	private Number countResultsByCriteria(Criteria criteria) {
		
		criteria.setProjection(Projections.rowCount());
		
		Number resultCount = (Number) criteria.uniqueResult();
		
		resetProjectionsInCriteria(criteria);
		
		return resultCount;
	}

	private void resetProjectionsInCriteria(Criteria criteria) {
		criteria.setProjection(null);
		return;
	}


	protected Number extractLevelFromUser(User referenceUser) throws ReflectiveOperationException 
	{
		Number levelValue = null;
		
		try
		{
			Role userRole = (Role) extractFieldFromUser(referenceUser, "role");
			
			Field field = userRole.getClass().getDeclaredField("level");
			
			field.setAccessible(true);
			levelValue = (Number) field.get(userRole);
			field.setAccessible(false);
			
			return levelValue;
		}
		catch (ReflectiveOperationException ex) 
		{
			throw ex;
		}
		
		
	}
	
	protected Object extractFieldFromUser(User user, String fieldName) throws ReflectiveOperationException
	{
		try
		{
			Object fieldValue;
			
			Field field = user.getClass().getDeclaredField(fieldName);
			
			field.setAccessible(true);
			fieldValue = field.get(user);
			field.setAccessible(false);

			return fieldValue;
		}
		catch (ReflectiveOperationException ex) 
		{
			throw ex;
		}
	}
	
	@Override
	public void insert(User user) throws DAOException {
		
		super.insert(user);
	}

	@Override
	public void update(User user) throws DAOException {
		
		super.update(user);
		
	}

	@Override
	public void delete(User user) throws DAOException {
		
		super.delete(user);
		
	}

	@Override
	public User getByAuth(String username, String password) throws DAOException {

		Session session = null;
		User account = null;
		
		try
		{
			session = openSession();
			
			Criteria criteria = session.createCriteria(User.class);
			
			criteria.add(Restrictions.like("username", username, MatchMode.EXACT));
			criteria.add(Restrictions.like("password", password, MatchMode.EXACT));
			
			account = (User) criteria.uniqueResult();
			
			session.flush();
		}
		catch(Exception ex)
		{
			String operationType = "GetByAuth";
			throw generateDAOException(ex, operationType);
		}
		finally
		{
			closeSession(session);
		}
		
		return account;
	}

	@Override
	public Integer getAll(List<User> resultList) throws DAOException {

		return getAll(resultList, null, null, null);
	}

	@Override
	public Integer getAll(List<User> resultList, Map<String, Object> filters,
			RecordSorting ordering, RecordPagination pagination)
			throws DAOException {
		
		Session session=null;
		
		try
		{
			session = openSession();
			
			Criteria criteria = session.createCriteria(User.class, this.defaultAliasName);

			addFilteringToCriteria(criteria, filters);
			
			Number numberOfRecords = countResultsByCriteria(criteria);

			addOrderingToCriteria(criteria, ordering);
			addPagingToCriteria(criteria, pagination);
			
			resultList.addAll((List<User>)criteria.list()); 
			
			session.flush();
			
			return numberOfRecords.intValue();
			
		}
		catch(Exception ex)
		{
			throw generateDAOException(ex, "GetAll");
		}
		finally
		{
			closeSession(session);
		}
	}
	
	@Override
	public Integer getEligibleSuper(List<User> resultList, User referenceUser) throws DAOException {
		// TODO Auto-generated method stub
		return getEligibleSuper(resultList, referenceUser, null, null, null);
	}

	@Override
	public Integer getEligibleSuper(List<User> resultList, User referenceUser,
			Map<String, Object> matchingFilter, RecordSorting ordering,
			RecordPagination pagination) throws DAOException {

		Session session=null;
		
		try
		{
			session = openSession();
			
			Criteria criteria = session.createCriteria(User.class, this.defaultAliasName);
			
			criteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);

			System.out.println(criteria.toString());
			
			addFilteringToCriteria(criteria, matchingFilter);
			System.out.println(criteria.toString());
			addSuperRestrictionToCriteria(criteria, referenceUser);
			System.out.println(criteria.toString());
			
			Number numberOfRecords = countResultsByCriteria(criteria);
			
			addOrderingToCriteria(criteria, ordering);
			addPagingToCriteria(criteria, pagination);
			
			System.out.println(criteria.toString());
			
			convertRecordListToUserList(resultList, criteria.list());
			
			session.flush();
			
			return  numberOfRecords.intValue();
		}
		catch(Exception ex)
		{
			throw generateDAOException(ex, "GetAll");
		}
		finally
		{
			closeSession(session);
		}
	}


	@Override
	public Integer getEligibleEnactors(List<User> resultList, User referenceUser)
			throws DAOException {
		return getEligibleEnactors(resultList, referenceUser, null, null, null);
	}

	@Override
	public Integer getEligibleEnactors(List<User> resultList,
			User referenceUser, Map<String, Object> matchingFilter,
			RecordSorting ordering, RecordPagination pagination)
			throws DAOException {

		Session session=null;
		
		try
		{
			session = openSession();
			
			Criteria criteria = session.createCriteria(User.class, this.defaultAliasName);

			addFilteringToCriteria(criteria, matchingFilter);
			addEnactorRestrictionToCriteria(criteria, referenceUser);
			
			Number numberOfRecords = countResultsByCriteria(criteria);

			addOrderingToCriteria(criteria, ordering);
			addPagingToCriteria(criteria, pagination);
			
			convertRecordListToUserList(resultList, criteria.list());
			
			session.flush();
			
			return numberOfRecords.intValue();
			
		}
		catch(Exception ex)
		{
			throw generateDAOException(ex, "GetAll");
		}
		finally
		{
			closeSession(session);
		}
	}

	private void convertRecordListToUserList(
			List<User> resultList, List criteriaList) 
	{
		if(criteriaList == null || criteriaList.isEmpty())
			return;
		
		if(isSingleItemRecord(criteriaList.get(0)))
		{
			convertRecordListToUserList_SingleItemRecord(resultList, criteriaList);
		}
		else
		{
			convertRecordListToUserList_MultiItemRecord(resultList, criteriaList);
		}
	}

	private boolean isSingleItemRecord(Object object) 
	{
		return (object instanceof Object[] ? false : true);
	}

	private void convertRecordListToUserList_SingleItemRecord(
			List<User> resultList, List criteriaList) 
	{
		Iterator it = criteriaList.iterator();
		Object record;
		while(it.hasNext())
		{
			resultList.add((User) it.next());
		}
	}
	

	private void convertRecordListToUserList_MultiItemRecord(
			List<User> resultList, List criteriaList) 
	{
		Iterator it = criteriaList.iterator();
		Object[] record;
		Integer index = 0;
		while(it.hasNext())
		{
			record = (Object[]) it.next();
			while(! record[index].getClass().equals(User.class) 
					&& index < record.length)
			{
				index++;
			}
			
			System.out.println("l" + record.length);
			resultList.add((User)record[index]);
		}
	}

	@Override
	public User getByUsername(String username) throws DAOException {

		Session session = null;
		User account = null;
		
		try
		{
			session = openSession();
			
			Criteria criteria = session.createCriteria(User.class);
			
			criteria.add(Restrictions.like("username", username, MatchMode.EXACT));
			
			account = (User) criteria.uniqueResult();
			
			session.flush();
		}
		catch(Exception ex)
		{
			String operationType = "GetByAuth";
			throw generateDAOException(ex, operationType);
		}
		finally
		{
			closeSession(session);
		}
		
		return account;
	}
}
